8.3 - Example mod made with Harmony
This is the complete code of the TreeRegrowChanger mod - made by GLaD0S.
This mod changes the regrowth factor of trees.
You can find the complete code on github here.
using Endnight.Extensions;
using HarmonyLib;
using Microsoft.VisualBasic;
using RedLoader;
using Sons.Gameplay;
using Sons.Gameplay.Artifact;
using Sons.Gameplay.TreeCutting;
using Sons.TerrainDetail;
using SonsSdk;
using SUI;
using System;
namespace TreeRegrowChanger;
public class TreeRegrowChanger : SonsMod
{
private static int EligibleTrees;
public TreeRegrowChanger()
{
HarmonyPatchAll = true; //It is important to enable Harmony patches if you want to use them.
}
protected override void OnInitializeMod()
{
Config.Init();
}
protected override void OnSdkInitialized()
{
SettingsRegistry.CreateSettings(this, null, typeof(Config));
}
//In this method we both adjust the result, skip the original method and we call another method we patched.
//We also never allow the original method to execute, this patch fully replaces the original method.
[HarmonyPatch(typeof(WorldObjectLocatorManager), "CheckRegrowTrees")]
private static class WorldObjectLocatorManagerCheckRegrowTreesPatch
{
//Here we reference all the original method arguments, so we can access and if necessary modify them.
private static bool Prefix( ref Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray __result,
float regrowthFactor,
int minRegrowTreesPerCheck = -1,
int maxRegrowTreesPerCheck = -1,
bool force = false)
{
WorldObjectLocatorManager worldObjectLocatorManager;
if (!WorldObjectLocatorManager.TryGetInstance(out worldObjectLocatorManager))
{
//The game expects an array of 2 integers, we return an empty array if we can't get the instance.
__result = new int[2];
return false; // Skip original method
}
if (!WorldObjectLocatorManager._treeRegrowth && !force)
{
__result = new int[2];
return false; // Skip original method
}
//The regrow factor is a percentage, the game iterates over random eligible trees until the desired percentage is reached.
//By defaul the game would always use 10 as the factor, but we can freely overwrite this value.
regrowthFactor = Config.RegrowFactor.Value / 100f;
//Call the next method with our changed values
__result = worldObjectLocatorManager.CheckRegrowTreesInternal(regrowthFactor, minRegrowTreesPerCheck, maxRegrowTreesPerCheck);
return false; // Skip original method
}
}
//We use this method to call the message at the correct time, this makes sure that we already have all the required values we need.
[HarmonyPatch(typeof(WorldObjectLocatorGroup), "TriggerRegrowth")]
private static class TriggerRegrowPatch
{
private static void Postfix(Il2CppSystem.Collections.Generic.List foundTreesToRegrow,
Il2CppSystem.Collections.Generic.List foundTreesToRegrowIndices,
int countToRegrow)
{
if (Config.ShowMessage.Value)
{
//This will display a message in the bottom left corner, informing the user how many trees regrew.
//Additionally we also display how many trees are eligible for regrowth.
//Doing this gives the user additional feedback and makes it easier to confirm if the mod is working as expected.
SonsTools.ShowMessage($"Regrown {countToRegrow} of {EligibleTrees} trees", 10f);
}
}
}
//In this method we count the trees that are eligible for regrowth.
[HarmonyPatch(typeof(WorldObjectLocatorManager), "CheckRegrowTreesInternal")]
private static class CheckRegrowTreesInternalPatch
{
//Here we fetch the instance of this specific WorldObjectLocatorManager, we then use it to fetch the states of the trees.
private static void Prefix(WorldObjectLocatorManager __instance)
{
Il2CppSystem.Collections.Generic.IReadOnlyDictionary states = __instance._saveData.GetStates();
Il2CppSystem.Collections.Generic.IReadOnlyDictionary unappliedStates = __instance._saveData.GetUnappliedStates();
//We set up our own counter on how many trees are eligible for regrowth.
int eligibleCount = 0;
Action checkTreeState = delegate (WorldObjectState worldObjectState)
{
if (worldObjectState == WorldObjectState.Destroyed)
{
eligibleCount++;
}
};
states.Values.ForEach(checkTreeState);
EligibleTrees = eligibleCount; //We store the count to use it later on in the displayed message.
}
}
}